home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Development Tools & Languages / MPW Related / Lurkers 1.6 / Lurkers.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-06-09  |  26.0 KB  |  925 lines  |  [TEXT/MPS ]

  1. /*----------------------------------------------------------------------------------------
  2.  
  3.      Lurkers
  4.      
  5.      Greg Anderson
  6.      January 1994
  7.      
  8.      AppleLink: G.ANDERSON
  9.      Internet:  greggor@apple.com
  10.      
  11.      About Lurkers:
  12.      
  13.      Lurkers searches a directory for files that are modifiable, including files
  14.      not in a project, files checked out for modification, and modify-read-only
  15.      files.
  16.      
  17.      Search modes (pick any one):
  18.      
  19.         -modifiable            (default) Search for files not in a project, files
  20.                             checked out for modification, and modify-read-only files
  21.         -notmodifiable        Search for files checked into a project that are not
  22.                             modifiable (has 'ckid', but is neither MRO nor checked out
  23.                             for modification)
  24.         -mro                Search for files that are checked into a project (has 'ckid'),
  25.                             but have been modified-read-only.
  26.         -insomeproject        Search for files with a 'ckid' resource--modifiable or not
  27.         -notinanyproject    Search for files without a 'ckid' resource
  28.         -modifiableinproject    Search for files with a 'ckid' resource that are MRO'ed or
  29.                             checked out for modification (does not actually check
  30.                             project database to see if any changes have been made, though)
  31.         -checkedoutmodifiable    Searches for files with a 'ckid' resource that are
  32.                             checked out for modification.
  33.     
  34.     Other flags:
  35.     
  36.         -r                    Search specified directory recursively
  37.         -s                    Short output (only list names of files that match
  38.                             the search criterion)
  39.         -f                    Output full pathnames
  40.         -q                    Don't quote filenames with special characters
  41.                             (Quoting only done in short output)
  42.         -rev                Show the revision number of the file
  43.         -textonly            Ignore files whose type is not TEXT
  44.         -ignore n            Ignore files and folders whose name is 'n'
  45.         
  46.     Revision History:
  47.     
  48.         1.0, 19 Jan 94        Initial release
  49.         1.1, 20 Jan 94        Lurkers being too strict in its test for checked out
  50.                             files, so it missed some.  Also, I failed to check
  51.                             for errors when opening the resource fork of a file
  52.                             (oops!), so files without a resource fork reported
  53.                             the information applicable to the next item in the
  54.                             resource chain (not a problem unless you check your
  55.                             MPW shell into a project--but our team does!)
  56.         1.2, 25 Jan 94        Lurkers still missing files; make its test even less
  57.                             strict (pretty sure I got it right this time)
  58.         1.3, 27 Jan 94        Added flags -f -q -rev -textonly
  59.         1.4, 16    Feb    94        Added partial pathnames, -checkedoutmodifiable, -mro
  60.         1.5, 2 Jun 94        Andy wanted me to remove some spurrious blank lines in -s mode
  61.         1.6, 9 Jun 94        Improved help, added -ignore, allowed single files to
  62.                             be tested
  63.         
  64.     To Do:
  65.     
  66.         Quoted files are not handled correctly; for example, a file that has
  67.         a single-quote in it needs to be escaped.  The current output is:
  68.         
  69.             ''filename''
  70.         
  71.         The desired output is:
  72.         
  73.             ''∂''filename'∂'''
  74.         
  75.         It would also be nice if -ignore handled wildcards, or even regular expresions
  76.  
  77. ----------------------------------------------------------------------------------------*/
  78.  
  79. #include    <Types.h>
  80. #include     <ctype.h>
  81. #include     <fcntl.h>
  82. #include     <string.h>
  83. #include     <stdio.h>
  84. #include    <StdLib.h>
  85. #include    <ErrMgr.h>
  86. #include    <CursorCtl.h>
  87. #include    <Errors.h>
  88. #include    <QuickDraw.h>
  89. #include    <SysEqu.h>
  90. #include    <Files.h>
  91. #include    <Memory.h>
  92. #include    <Resources.h>
  93. #include    <CursorCtl.h>
  94.  
  95. //
  96. // First line of usage string (more than this is printed out)
  97. //
  98. static    char*    usage = "%s [file's projector status] [option…] directory|file…\n";
  99.  
  100. static    long    optionsSpecified = false;
  101.  
  102. //
  103. // We know a little bit about the contents of a ckid resource:
  104. //
  105. struct ckidheader
  106. {
  107.     long        fUnknownID;                // Different after every projector operation
  108.     long        fUnkownConstant;        // Always the same
  109.     
  110.     short        fUnknown1;                // Usually 0004
  111.     short        fIsCheckedOut;            // Usually 2, 3 or 4 if checked out, always 0 if not
  112.     
  113.     short        fIsMRO;                    // 0001 if MRO, 0000 if not 
  114.     short        fUnknown2;                // Usually 0000
  115.     
  116.     long        fUnknown3;                // I don't know or care what these are
  117.     long        fUnknown4;
  118.     long        fUnknown5;
  119.     long        fUnknown6;
  120.     long        fUnknown7;
  121.     long        fUnknown8;
  122.     
  123.     unsigned char fProjectName;            // null-terminated pascal string
  124.     
  125.                                         // After the project name is the name of the
  126.                                         // person who checked out this file on this
  127.                                         // machine (no relation to the person who currently
  128.                                         // has the file checked out).  Also null-terminated
  129.                                         
  130.                                         // After the user name is the revision number
  131.                                         // of the file, in ascii and null-terminated.
  132.     
  133. };
  134.  
  135. //
  136. // Projector states that we know about:
  137. // (One and only one of these bits may be set at any time, as we switch on them)
  138. //
  139. #define kNotModifiable            1
  140. #define kModifiedReadOnly        2
  141. #define kCheckedOut                4
  142. #define kNotInAProject            8
  143.  
  144. //
  145. // This mask must include all of the file project states above
  146. //
  147. #define kFileProjectMask        (kNotModifiable | kModifiedReadOnly | kCheckedOut | kNotInAProject)
  148.  
  149. //
  150. // Here are some extra file project states that we do not switch on
  151. // (so these bits may be set in addition to the ones above)
  152. //
  153. #define kHasCKIDAndIsModifiable    0x10
  154. #define kHasCKIDAndIsCheckedOut    0x20
  155.  
  156. //
  157. // Global options, because there is no need to pass it around
  158. //
  159. long gOptions = 0;
  160. long gWantOutputForState = kModifiedReadOnly | kCheckedOut | kNotInAProject;
  161.  
  162. #define kShortOutput        1
  163. #define kRecursive            2
  164. #define kOutputRevision        4
  165. #define kTextOnly            8
  166. #define kFullPathnames        0x10
  167. #define kDontQuote            0x20
  168.  
  169. #define kMagicUnusedDirID    0x8000000
  170.  
  171. //
  172. // Some global variables that should be local, but I don't
  173. // want my stack to get too large, and I don't feel like
  174. // being clever.
  175. //
  176. // The code is very carefully written to avoid using these
  177. // variables after they have been reused in a recursive call.
  178. // This is EVIL, but we want to avoid having a large stack.
  179. //
  180. CInfoPBRec pb;
  181. Str255 gFilename;
  182. Str255 gFolderpath;
  183.  
  184. Str255 gTempProjectName;
  185. Str31 gTempRevisionNumber;
  186. char gTempRevisionString[64];
  187.  
  188. char** gIgnoreList;
  189. short gNumberOfIgnoreItems = 0;
  190.  
  191. #define ConvertToUppercase(c)    (((c >= 'a') && (c <= 'z')) ? c - 'a' + 'A' : c)
  192.  
  193. //----------------------------------------------------------------------------------------
  194. // OptionSpecified: 
  195. //----------------------------------------------------------------------------------------
  196. Boolean OptionSpecified(long flag)
  197. {
  198.     return ((gOptions & flag) != 0);
  199. } // OptionSpecified 
  200.  
  201. //----------------------------------------------------------------------------------------
  202. // ItemMatches
  203. //
  204. // It would be nice if this checked for wildcards...
  205. //----------------------------------------------------------------------------------------
  206. Boolean ItemMatches(Str255 testItem, char* compareWith)
  207. {
  208.     Boolean itemMatches = true;
  209.     short i;
  210.     
  211.     for(i=1; i<=testItem[0]; ++i)
  212.     {
  213.         unsigned char c1 = testItem[i];
  214.         unsigned char c2 = *compareWith++;
  215.         if(ConvertToUppercase(c1) != ConvertToUppercase(c2))
  216.         {
  217.             itemMatches = false;
  218.             break;
  219.         }
  220.     }
  221.     
  222.     //
  223.     // If we ran out of characters in 'testItem', but there
  224.     // are still characters left in 'compareWith', then the
  225.     // two items don't really match
  226.     //
  227.     if(*compareWith != 0)
  228.         itemMatches = false;
  229.     
  230.     return itemMatches;
  231. } // ItemMatches
  232.  
  233. //----------------------------------------------------------------------------------------
  234. // ItemIsInIgnoreList
  235. //
  236. // Return 'true' if the specified string is in the ignore list
  237. //----------------------------------------------------------------------------------------
  238. Boolean ItemIsInIgnoreList(Str255 testItem)
  239. {
  240.     Boolean shouldIgnore = false;
  241.     short i;
  242.     
  243.     for(i=0; i< gNumberOfIgnoreItems; ++i)
  244.     {
  245.         if(ItemMatches(testItem, gIgnoreList[i]))
  246.         {
  247.             shouldIgnore = true;
  248.             break;
  249.         }
  250.     }
  251.     
  252.     return shouldIgnore;
  253. } // ItemIsInIgnoreList
  254.  
  255. //----------------------------------------------------------------------------------------
  256. // BuildFolderPathname: 
  257. //----------------------------------------------------------------------------------------
  258. void BuildFolderPathname(short vRefNum, long dirID, Str255 folderpath, long stopDirID, Boolean firstTime)
  259. {
  260.     Str63 thisName;
  261.     OSErr err = noErr;
  262.     
  263.     thisName[0] = 0;
  264.     folderpath[0] = 0;
  265.         
  266.     pb.dirInfo.ioCompletion            = nil;
  267.     pb.dirInfo.ioNamePtr            = thisName;
  268.     pb.dirInfo.ioResult                = noErr;
  269.     pb.dirInfo.ioVRefNum            = vRefNum;
  270.     pb.dirInfo.ioDrDirID            = dirID;
  271.     pb.dirInfo.ioFDirIndex            = -1;
  272.     
  273.     err = PBGetCatInfo(&pb,false);
  274.     
  275.     if(err == noErr)
  276.     {
  277.         if(dirID != stopDirID)
  278.         {
  279.             BuildFolderPathname(vRefNum, pb.dirInfo.ioDrParID, folderpath, stopDirID, false);
  280.  
  281.             //
  282.             // Append 'thisName' onto folderpath
  283.             //
  284.             thisName[thisName[0] + 1] = 0;
  285.             strcpy(folderpath + folderpath[0] + 1, thisName + 1);
  286.             folderpath[0] += thisName[0];
  287.             
  288.             //
  289.             // Append a colon onto folderpath
  290.             //
  291.             folderpath[folderpath[0] + 1] = ':';
  292.             ++folderpath[0];
  293.         }
  294.         else if(!firstTime)
  295.         {
  296.             folderpath[1] = ':';
  297.             folderpath[0] = 1;
  298.         }
  299.     }
  300. } // BuildFolderPathname 
  301.  
  302. //----------------------------------------------------------------------------------------
  303. // HasSpecialCharacters: 
  304. //----------------------------------------------------------------------------------------
  305. Boolean HasSpecialCharacters(Str255 pstring)
  306. {
  307.     Boolean hasSpecial = false;
  308.     short i;
  309.     
  310.     for(i=1;i<= pstring[0]; ++i)
  311.     {
  312.         switch(pstring[i])
  313.         {
  314.         case ' ':
  315.         case '\"':
  316.         case '\∂':
  317.             hasSpecial = true;
  318.             break;
  319.         }
  320.     }
  321.     
  322.     return hasSpecial;
  323. } // HasSpecialCharacters 
  324.  
  325. //----------------------------------------------------------------------------------------
  326. // ExamineProjectorInformation: 
  327. //----------------------------------------------------------------------------------------
  328. long ExamineProjectorInformation(short vRefNum, long dirID, const Str255 fileName, Str255 projectName, Str31 revisionNumber)
  329. {
  330.     Handle ckidHandle = nil;
  331.     short resRefNum;
  332.     long projectorInfo = kNotInAProject;
  333.     
  334.     projectName[0] = 0;
  335.     revisionNumber[0] = 0;
  336.     
  337.     resRefNum = HOpenResFile(vRefNum, dirID, fileName, fsRdPerm);
  338.     
  339.     if(resRefNum != -1)
  340.     {
  341.         ckidHandle = Get1Resource('ckid', 128);
  342.         if(ckidHandle != nil)
  343.         {
  344.             struct ckidheader* ckpeek = nil;
  345.             unsigned char* p;
  346.             
  347.             HLock(ckidHandle);
  348.             ckpeek = *( (struct ckidheader**)ckidHandle);
  349.             
  350.             //
  351.             // Look at a bit of the ckid resource
  352.             //
  353.             if(ckpeek->fIsCheckedOut != 0)
  354.                 projectorInfo = kCheckedOut;
  355.             else if((ckpeek->fIsMRO & 1) != 0)
  356.                 projectorInfo = kModifiedReadOnly;
  357.             else
  358.                 projectorInfo = kNotModifiable;
  359.             
  360.             //
  361.             // Set the 'changed' bit if this item
  362.             // is modifiable (we know that it is in a project)
  363.             //
  364.             if((projectorInfo & (kModifiedReadOnly | kCheckedOut)) != 0)
  365.                 projectorInfo |= kHasCKIDAndIsModifiable;
  366.             if((projectorInfo & kCheckedOut) != 0)
  367.                 projectorInfo |= kHasCKIDAndIsCheckedOut;
  368.             
  369.             //
  370.             // Hack:  'projectName' and fProjectName are
  371.             // pascal strings, but they are always
  372.             // null-terminated, so we can strcpy it
  373.             //
  374.             strcpy(projectName, &ckpeek->fProjectName);
  375.                         
  376.             //
  377.             // The string past the project name is the user name
  378.             // (add one to skip the null termination)
  379.             //
  380.             p = &ckpeek->fProjectName + strlen(&ckpeek->fProjectName) + 1;
  381.             
  382.             //
  383.             // The string past the user name is the revision number
  384.             // (add one to skip the null termination)
  385.             // Once again, we use strcpy, as the string is always
  386.             // null-terminated.
  387.             //
  388.             p = p + strlen(p) + 1;
  389.             strcpy(revisionNumber, p);
  390.             
  391.             HUnlock(ckidHandle);
  392.         }
  393.         
  394.         CloseResFile(resRefNum);
  395.     }
  396.     //else
  397.     //    fprintf(stderr, "### Error opening resource fork of %P", fileName);
  398.     
  399.     return projectorInfo;
  400. } // ExamineProjectorInformation 
  401.  
  402. //----------------------------------------------------------------------------------------
  403. // ProcessFile: 
  404. //----------------------------------------------------------------------------------------
  405. void ProcessFile(short vRefNum, long dirID, Str255 folderpath, Str255 filename, Boolean* didOutput)
  406. {
  407.     char commaString[4];
  408.     char quoteString[4];
  409.     long fileProjectStatus = 0;
  410.     
  411.     //
  412.     // Don't do a thing with this item if it's in the ignore list
  413.     //
  414.     if(ItemIsInIgnoreList(filename) == false)
  415.     {
  416.         //
  417.         // Find out if the file has a 'ckid' resource.  While we're at it,
  418.         // extract information about the file (checked out, MRO'ed, etc, project
  419.         // name, revision number)
  420.         //
  421.         fileProjectStatus = ExamineProjectorInformation(vRefNum, dirID, filename, gTempProjectName, gTempRevisionNumber);
  422.         
  423.         //
  424.         // Ignore the file unless we want output for its state
  425.         //
  426.         if((gWantOutputForState & fileProjectStatus) != 0)
  427.         {
  428.             //
  429.             // If doing short output (-s), only print the name of the file
  430.             //
  431.             if(OptionSpecified(kShortOutput))
  432.             {
  433.                 //
  434.                 // Print revision numbers if requested (-rev)
  435.                 //
  436.                 if((OptionSpecified(kOutputRevision)) && (gTempRevisionNumber[0] != 0))
  437.                 {
  438.                     strcpy(commaString, ",");
  439.                 }
  440.                 else
  441.                 {
  442.                     commaString[0] = 0;
  443.                     gTempRevisionNumber[0] = 0;
  444.                 }
  445.                 
  446.                 //
  447.                 // Quote the string if necessary
  448.                 //
  449.                 if((OptionSpecified(kDontQuote) == false) && (HasSpecialCharacters(folderpath) || HasSpecialCharacters(filename)))
  450.                     strcpy(quoteString, "\'");
  451.                 else
  452.                     quoteString[0] = 0;
  453.                 
  454.                 fprintf(stdout, "%s%P%P%s%P%s\n", quoteString, folderpath, filename, commaString, gTempRevisionNumber, quoteString);
  455.             }
  456.             else
  457.             {
  458.                 //
  459.                 // At this point we are commiting to printing something; if we
  460.                 // have not printed anything yet, then add an extra blank line
  461.                 // so things look better
  462.                 //
  463.                 if(*didOutput == false)
  464.                 {
  465.                     fprintf(stdout, "\n");
  466.                 }
  467.     
  468.                 if(OptionSpecified(kOutputRevision))
  469.                     sprintf(gTempRevisionString, "(%P,%P) ", filename, gTempRevisionNumber);
  470.                 else
  471.                     gTempRevisionString[0] = 0;
  472.                 
  473.                 switch(fileProjectStatus & kFileProjectMask)
  474.                 {
  475.                 case kNotInAProject:
  476.                     fprintf(stdout, "File \"%P%P\"; Line # is not in a project\n", folderpath, filename);
  477.                     break;
  478.                 
  479.                 case kNotModifiable:
  480.                     fprintf(stdout, "File \"%P%P\"; Line # %sis an unmodifiable copy of a file in the project %P\n", folderpath, filename, gTempRevisionString, gTempProjectName);
  481.                     break;
  482.                     
  483.                 case kModifiedReadOnly:
  484.                     fprintf(stdout, "File \"%P%P\"; Line # %sis a modify-read-only copy of a file in the project %P\n", folderpath, filename, gTempRevisionString, gTempProjectName);
  485.                     break;
  486.                 
  487.                 case kCheckedOut:
  488.                     fprintf(stdout, "File \"%P%P\"; Line # %sis a modifiable copy of a file in the project %P\n", folderpath, filename, gTempRevisionString, gTempProjectName);
  489.                     break;
  490.                 
  491.                 default:
  492.                     fprintf(stdout, "File \"%P%P\"; Line # %sis not recognized by Lurkers\n", folderpath, filename, gTempRevisionString);
  493.                     break;
  494.                 }
  495.                 
  496.                 //
  497.                 // didOutput actually means "printed a long line"; short lines don't
  498.                 // count.  (This flag is used to determine if a trailing blank line
  499.                 // should be added to the output)
  500.                 //
  501.                 *didOutput = true;
  502.             }
  503.         }
  504.     }
  505. } // ProcessFile 
  506.  
  507. //----------------------------------------------------------------------------------------
  508. // Lurkers: 
  509. //----------------------------------------------------------------------------------------
  510. void Lurkers(short vRefNum, long dirID, long startLurkingDirID)
  511. {
  512.     OSErr err = noErr;
  513.     short index = 1;
  514.     Boolean didOutput = false;
  515.     
  516.     //
  517.     // Build the pathname to this folder if full paths were
  518.     // specified or if we're not doing short output
  519.     //
  520.     gFolderpath[0] = 0;
  521.     if(OptionSpecified(kFullPathnames) || OptionSpecified(kShortOutput))
  522.         BuildFolderPathname(vRefNum, dirID, gFolderpath, OptionSpecified(kShortOutput) && !OptionSpecified(kFullPathnames) ? startLurkingDirID : kMagicUnusedDirID, true);
  523.  
  524.     //
  525.     // If doing long output, tell the user what we're doing
  526.     //
  527.     if(OptionSpecified(kShortOutput) == false)
  528.     {
  529.         fprintf(stdout, "### Scanning folder \"%P\"\n", gFolderpath);
  530.         fflush(stdout);
  531.     }
  532.     
  533.     //
  534.     // Get rid of the pathname if we're not going to output it.
  535.     //
  536.     //if(OptionSpecified(kFullPathnames) == false)
  537.     //    gFolderpath[0] = 0;
  538.     
  539.     //
  540.     // Walk every file in this directory
  541.     //
  542.     while(err == noErr)
  543.     {
  544.         SpinCursor(1);
  545.         
  546.         gFilename[0] = 0;
  547.         
  548.         pb.hFileInfo.ioCompletion        = nil;
  549.         pb.hFileInfo.ioNamePtr            = gFilename;
  550.         pb.hFileInfo.ioResult            = noErr;
  551.         pb.hFileInfo.ioVRefNum            = vRefNum;
  552.         pb.hFileInfo.ioDirID            = dirID;
  553.         pb.hFileInfo.ioFDirIndex        = index;
  554.         
  555.         err = PBGetCatInfo(&pb,false);
  556.         
  557.         //
  558.         // We only care about files right now...
  559.         //
  560.         if((err == noErr) && ((pb.hFileInfo.ioFlAttrib & (1 << 4)) == 0))
  561.         {
  562.             //
  563.             // If 'kTextOnly' is set, only process the file if its type
  564.             // is 'TEXT'.  Otherwise, always process the file
  565.             //
  566.             if( (OptionSpecified(kTextOnly) == false) || (pb.hFileInfo.ioFlFndrInfo.fdType == 'TEXT'))
  567.                 ProcessFile(vRefNum, dirID, gFolderpath, gFilename, &didOutput);
  568.         }
  569.         
  570.         ++index;
  571.     }
  572.     
  573.     //
  574.     // Add another blank line if there was any output,
  575.     // then flush stdout so that the text is actually
  576.     // printed
  577.     //
  578.     if(didOutput)
  579.     {
  580.         fprintf(stdout, "\n");
  581.     }
  582.     fflush(stdout);
  583.     
  584.     //
  585.     // Do we want to do a deep search?
  586.     //
  587.     if(OptionSpecified(kRecursive))
  588.     {
  589.         index = 1;
  590.         err = noErr;
  591.         
  592.         //
  593.         // Walk every folder in this directory
  594.         //
  595.         while(err == noErr)
  596.         {
  597.             gFilename[0] = 0;
  598.             
  599.             pb.dirInfo.ioCompletion            = nil;
  600.             pb.dirInfo.ioNamePtr            = gFilename;
  601.             pb.dirInfo.ioResult                = noErr;
  602.             pb.dirInfo.ioVRefNum            = vRefNum;
  603.             pb.dirInfo.ioDrDirID            = dirID;
  604.             pb.dirInfo.ioFDirIndex            = index;
  605.             
  606.             err = PBGetCatInfo(&pb,false);
  607.             
  608.             //
  609.             // Call Lurkers again on every folder we find...
  610.             //
  611.             if((err == noErr) && ((pb.hFileInfo.ioFlAttrib & (1 << 4)) != 0))
  612.             {
  613.                 //
  614.                 // Don't do the recursive call if this folder's name
  615.                 // appears in the ignore list
  616.                 //
  617.                 if(ItemIsInIgnoreList(gFilename) == false)
  618.                 {
  619.                     Lurkers(vRefNum, pb.dirInfo.ioDrDirID, startLurkingDirID);
  620.                 }
  621.             }
  622.             
  623.             ++index;
  624.         }
  625.     }
  626. } // Lurkers 
  627.  
  628.  
  629. //----------------------------------------------------------------------------------------
  630. // main: 
  631. //----------------------------------------------------------------------------------------
  632. main( int argc, char* argv[] )
  633. {
  634.     Boolean                    optionsSpecified = false;
  635.     Boolean                    didLurkers = false;
  636.     short                    vRefNum = 0;
  637.     long                    dirID = 0;
  638.     short                    parms;
  639.     short                    status = 0;
  640.     Boolean*                processedParameters;
  641.     OSErr                    err = noErr;
  642.     
  643.     InitCursorCtl(nil);
  644.  
  645.     //
  646.     // Set up the ignore list and the set of flags that indicates which
  647.     // parameters we've processed.  Both arrays only need to be 'argc'
  648.     // elements in size, or smaller.
  649.     //
  650.     processedParameters = (Boolean*)NewPtr(sizeof(Boolean) * (argc + 1));
  651.     gIgnoreList = (char**)NewPtr(sizeof(Ptr) * (argc + 1));
  652.     for( parms = 0; parms < argc; ++parms )
  653.     {
  654.         processedParameters[parms] = false;
  655.         gIgnoreList[parms] = nil;
  656.     }
  657.     
  658.     //
  659.     // Run through the parameters once looking for things that start with "-"
  660.     //
  661.     for( parms = 1; parms < argc; parms++ )
  662.     {
  663.         short length = strlen(argv[parms]);
  664.         
  665.         //
  666.         // Look at all of the parameters that have a dash
  667.         //
  668.         if( argv[parms][0] == '-')
  669.         {
  670.             processedParameters[parms] = true;
  671.             
  672.             //
  673.             // Process the specific flags
  674.             //
  675.             if( strcmp( argv[parms], "-modifiable" ) == 0 )
  676.             {
  677.                 optionsSpecified = true;
  678.                 gWantOutputForState = kModifiedReadOnly | kCheckedOut | kNotInAProject;
  679.             }
  680.             else if( strcmp( argv[parms], "-notmodifiable" ) == 0 )
  681.             {
  682.                 optionsSpecified = true;
  683.                 gWantOutputForState = kNotModifiable;
  684.             }
  685.             else if( strcmp( argv[parms], "-mro" ) == 0 )
  686.             {
  687.                 optionsSpecified = true;
  688.                 gWantOutputForState = kModifiedReadOnly;
  689.             }
  690.             else if( strcmp( argv[parms], "-notinanyproject" ) == 0 )
  691.             {
  692.                 optionsSpecified = true;
  693.                 gWantOutputForState = kNotInAProject;
  694.             }
  695.             else if( strcmp( argv[parms], "-insomeproject" ) == 0 )
  696.             {
  697.                 optionsSpecified = true;
  698.                 gWantOutputForState = kModifiedReadOnly | kCheckedOut | kNotModifiable;
  699.             }
  700.             else if( strcmp( argv[parms], "-modifiableinproject" ) == 0 )
  701.             {
  702.                 optionsSpecified = true;
  703.                 gWantOutputForState = kHasCKIDAndIsModifiable;
  704.             }
  705.             else if( strcmp( argv[parms], "-checkedoutmodifiable" ) == 0 )
  706.             {
  707.                 optionsSpecified = true;
  708.                 gWantOutputForState = kHasCKIDAndIsCheckedOut;
  709.             }
  710.             else if( strcmp( argv[parms], "-r" ) == 0 )
  711.             {
  712.                 optionsSpecified = true;
  713.                 gOptions |= kRecursive;
  714.             }
  715.             else if( strcmp( argv[parms], "-s" ) == 0 )
  716.             {
  717.                 optionsSpecified = true;
  718.                 gOptions |= kShortOutput;
  719.             }
  720.             else if( strcmp( argv[parms], "-f" ) == 0 )
  721.             {
  722.                 optionsSpecified = true;
  723.                 gOptions |= kFullPathnames;
  724.             }
  725.             else if( strcmp( argv[parms], "-q" ) == 0 )
  726.             {
  727.                 optionsSpecified = true;
  728.                 gOptions |= kDontQuote;
  729.             }
  730.             else if( strcmp( argv[parms], "-rev" ) == 0 )
  731.             {
  732.                 optionsSpecified = true;
  733.                 gOptions |= kOutputRevision;
  734.             }
  735.             else if( strcmp( argv[parms], "-textonly" ) == 0 )
  736.             {
  737.                 optionsSpecified = true;
  738.                 gOptions |= kTextOnly;
  739.             }
  740.             else if( strcmp( argv[parms], "-ignore" ) == 0 )
  741.             {
  742.                 optionsSpecified = true;
  743.                 ++parms;
  744.                 if(parms >= argc)
  745.                 {
  746.                     fprintf(stderr,"### %s - Encountered -ignore without a parameter", argv[0]);
  747.                 }
  748.                 else
  749.                 {
  750.                     processedParameters[parms] = true;
  751.                     gIgnoreList[gNumberOfIgnoreItems++] = argv[parms];
  752.                 }
  753.             }
  754.             else
  755.             {
  756.                 fprintf(stderr,"### %s - \"%s\" is not an option.\n", argv[0], argv[parms]);
  757.                 status = 1;
  758.                 
  759.                 break;
  760.             }
  761.         }
  762.     }
  763.     
  764.     //
  765.     // If neither -f nor -s, then -f
  766.     //
  767.     if((OptionSpecified(kFullPathnames) == false) && (OptionSpecified(kShortOutput) == false))
  768.         gOptions |= kFullPathnames;
  769.     
  770.     //
  771.     // If all of the "-" parameters were processed okay, then run through
  772.     // the parameters again looking for directory names
  773.     //
  774.     if(status == 0)
  775.     {
  776.         if((OptionSpecified(kShortOutput) == false))
  777.         {
  778.             fprintf(stdout, "\n");
  779.             fflush(stdout);
  780.         }
  781.  
  782.         for( parms = 1; parms < argc; parms++ )
  783.         {            
  784.             //
  785.             // If we didn't process this parameter the first time through the
  786.             // loop, then we assume it is the name of a directory search
  787.             // or a file to test
  788.             //
  789.             if(processedParameters[parms] == false)
  790.             {
  791.                 short length = strlen(argv[parms]);
  792.                 FSSpec tempFSSpec;
  793.                 
  794.                 strcpy(gFilename+1, argv[parms]);
  795.                 gFilename[0] = length;
  796.                 
  797.                 //
  798.                 // Make an FSSpec so that we can get the vRefNum of the specified
  799.                 // directory.
  800.                 //
  801.                 err = FSMakeFSSpec(0, 0, gFilename, &tempFSSpec);
  802.                 if(err == noErr)
  803.                 {
  804.                     vRefNum = tempFSSpec.vRefNum;
  805.                     
  806.                     //
  807.                     // Use PBGetCatInfo to get the dirID of the specified folder
  808.                     //
  809.                     pb.dirInfo.ioCompletion            = nil;
  810.                     pb.dirInfo.ioNamePtr            = &tempFSSpec.name;
  811.                     pb.dirInfo.ioResult                = noErr;
  812.                     pb.dirInfo.ioVRefNum            = vRefNum;
  813.                     pb.dirInfo.ioDrDirID            = tempFSSpec.parID;
  814.                     pb.dirInfo.ioFDirIndex            = 0;
  815.                     
  816.                     err = PBGetCatInfo(&pb,false);
  817.                 }
  818.                 
  819.                 //
  820.                 // Run Lurkers as soon as we get a directory or filename
  821.                 // (this is an easy and sleazy way to support multiple
  822.                 // folders/files on the command line)
  823.                 //
  824.                 if(err == noErr)
  825.                 {
  826.                     //
  827.                     // Is this a file or a folder?
  828.                     //
  829.                     if((pb.hFileInfo.ioFlAttrib & (1 << 4)) != 0)
  830.                     {
  831.                         dirID = pb.dirInfo.ioDrDirID;
  832.                         if(ItemIsInIgnoreList(tempFSSpec.name) == false)
  833.                         {
  834.                             Lurkers(vRefNum, dirID, dirID);
  835.                         }
  836.                     }
  837.                     else
  838.                     {
  839.                         Boolean unusedDidOutput;
  840.                         dirID = pb.hFileInfo.ioFlParID;
  841.                         
  842.                         //
  843.                         // If 'kTextOnly' is set, only process the file if its type
  844.                         // is 'TEXT'.  Otherwise, always process the file
  845.                         //
  846.                         if( (OptionSpecified(kTextOnly) == false) || (pb.hFileInfo.ioFlFndrInfo.fdType == 'TEXT'))
  847.                         {
  848.                             //
  849.                             // At this point, 'gFileName' contains the file exactly as
  850.                             // the user specified it. Copy the filename from the temporary
  851.                             // FSSpec, which contains only the name.
  852.                             //
  853.                             Size len = tempFSSpec.name[0] + 1;
  854.                             BlockMove((Ptr)tempFSSpec.name, (Ptr)gFilename, len);
  855.  
  856.                             //
  857.                             // Build the pathname to this folder if full paths were specified
  858.                             //
  859.                             gFolderpath[0] = 0;
  860.                             if(OptionSpecified(kFullPathnames))
  861.                             {
  862.                                 BuildFolderPathname(vRefNum, dirID, gFolderpath, kMagicUnusedDirID, true);
  863.                             }
  864.                             // fprintf(stderr, "### About to process <%P> in folder <%P> dirID <%ld>\n", gFilename, gFolderpath, dirID);
  865.     
  866.                             ProcessFile(vRefNum, dirID, gFolderpath, gFilename, &unusedDidOutput);
  867.                         }
  868.                     }
  869.                     
  870.                     //
  871.                     // Make a note of the fact that we ran lurkers
  872.                     //
  873.                     didLurkers = true;
  874.                 }
  875.                 else
  876.                 {
  877.                     //
  878.                     // Set 'didLurkers' if we tried to run lurkers, too
  879.                     //
  880.                     fprintf(stderr, "### Error %d accessing %P\n", err, gFilename);
  881.                     didLurkers = true;
  882.                 }
  883.             }
  884.         }
  885.     }
  886.     
  887.     //
  888.     // if there were errors in the parameters, print usage
  889.     //
  890.     if((status == 1) || (didLurkers == false))
  891.     {        
  892.         fprintf(stderr, usage, argv[0]);
  893.         
  894.         //
  895.         // Even more usage information for Andy...
  896.         //
  897.         fprintf(stderr, "\t©1994 Apple Computer, Inc. by Greg Anderson\n");
  898.         fprintf(stderr, "\n");
  899.         fprintf(stderr, "\tFile's projector status:\n");
  900.         fprintf(stderr, "\t\t-insomeproject\t\t\t# File is checked in to some project (has ckid)\n");
  901.         fprintf(stderr, "\t\t-notinanyproject\t\t# File is not checked in to any project (no ckid)\n");
  902.         fprintf(stderr, "\t\t-checkedoutmodifiable\t# File is checked in to some project and is\n");
  903.         fprintf(stderr, "\t\t\t\t\t\t\t\t#\tcurrently checked out for modification (but not MRO'ed)\n");
  904.         fprintf(stderr, "\t\t-modifiableinproject\t# Synonym for -checkedoutmodifiable\n");
  905.         fprintf(stderr, "\t\t-modifiable\t\t\t\t# Not in a project, in a project and checked out for modification,\n");
  906.         fprintf(stderr, "\t\t\t\t\t\t\t\t#\tor in a project and checked out modified-read-only\n");
  907.         fprintf(stderr, "\t\t-notmodifiable\t\t\t# In a project and not modifiable\n");
  908.         fprintf(stderr, "\t\t-mro\t\t\t\t\t# In a project and modified-read-only\n");
  909.         fprintf(stderr, "\n");
  910.         fprintf(stderr, "\tOptions:\n");
  911.         fprintf(stderr, "\t\t-r\t\t\t\t\t\t# Recursive search\n");
  912.         fprintf(stderr, "\t\t-s\t\t\t\t\t\t# Short output\n");
  913.         fprintf(stderr, "\t\t-q\t\t\t\t\t\t# Never quote filenames\n");
  914.         fprintf(stderr, "\t\t-f\t\t\t\t\t\t# Output full pathnames\n");
  915.         fprintf(stderr, "\t\t-rev\t\t\t\t\t# Output file's projector revision number (if any)\n");
  916.         fprintf(stderr, "\t\t-textonly\t\t\t\t# Ignore files whose type is not TEXT\n");
  917.         fprintf(stderr, "\t\t-ignore n\t\t\t\t# Ignore files and folders whose name is \"n\"\n");
  918.         fprintf(stderr, "\n");
  919.         fprintf(stderr, "\tSpecifying a file tests only that file.  Specifying a directory tests every\n");
  920.         fprintf(stderr, "\tfile in that directory (and every file anywhere in that hierarchy if -r is used).\n");
  921.     }
  922.     
  923.     return status;
  924. } // main 
  925.